一、MyBatis 中 ${} 和 #{} 的区别
1.1 ${}
和#{}
演示
数据库数据:
dao 接口:
Mapper.xml:
执行测试代码:
查看执行结果:
发现都能够查询出来
1.2 SQL 注入问题
${}
会产生 SQL 注入,#{}
不会产生 SQL 注入问题
我们做一个测试:
查询生成的 SQL 语句:
我们传递的参数是 aaa’ or 1=1 —,导致查询出来了全部的数据。
大家可以想象一下,如果我是要根据 id 删除呢?
如果我传递的是:1’ or 1=1; —,结果会是什么样,我想大家应该已经知道了。
我这里 id 是 Integer 类型,不好测试,就不带大家测试了,大家有兴趣可以自己私下测试。
如果上面使用的是#{}
就不会出现 SQL 注入的问题了
1.3 ${}
和#{}
的区别
#{}
匹配的是一个占位符,相当于 JDBC 中的一个?,会对一些敏感的字符进行过滤,编译过后会对传递的值加上双引号,因此可以防止 SQL 注入问题。
${}
匹配的是真实传递的值,传递过后,会与 sql 语句进行字符串拼接。${} 会与其他 sql 进行字符串拼接,不能预防 sql 注入问题。
查看#{}
和${}
生成的 SQL 语句:
1.4 #{}
底层是如何防止 SQL 注入的?
1.4.1 网上的答案
网上关于这类问题非常多,总结出来就两个原因:
1)#{}
底层采用的是 PreparedStatement,会预编译,因此不会产生 SQL 注入问题;
其实预编译是 MySQL 自己本身的功能,和 PreparedStatement 没关系;而且预编译也不是咱们理解的那个预编译,再者 PreparedStatement 底层默认根本没有用到预编译(要我们手动开启)!详细往下看
2)#{}
不会产生字符串拼接,${}
会产生字符串拼接,因此${}
会出现 SQL 注入问题;
这两个答案都经不起深究,最终答案也只是停留在表面,也没人知道具体是为什么。
1.4.2 为什么能防止 SQL 注入?
我们翻开 MySQL 驱动的源码一看究竟;
打开 PreparedStatement 类的 setString() 方法(MyBatis 在#{}
传递参数时,是借助 setString() 方法来完成,${}
则不是):
setString() 方法全部源码:
我们执行#{}
的查询语句,打断点观察:
最终传递的参数如下:
最终传递的参数为:'aaa\' or 1=1 --
咱们在数据库中执行如下 SQL 语句(肯定是查询不到数据的):
如果把 PreparedStatement 加的那根 ”/” 去掉呢?我们执行 SQL 试试:
我们也可以通过 MySQL 的日志来观察#{}
和${}
产生的 SQL 语句来分析问题:
1)开启 MySQL 日志:
在 MySQL 配置文件中的 [mysqld] 下增加如下配置:
2)重启 MySQL 服务(要以管理员身份运行):
使用 mybatis 分别执行如下两条 SQL 语句:
查看 MySQL 日志:
1.5 #{}
和${}
的应用场景
既然#{}
比${}
好那么多,那为什么还要有${}
这个东西存在呢?干脆都用#{}
不就万事大吉吗?
其实不是的,${}
也有用武之地,我们都知道${}
会产生字符串拼接,来生成一个新的字符串
1.5.1 ${} 和 #{} 用法上的区别
例如现在要进行模糊查询,查询 user 表中姓张的所有员工的信息
sql 语句为:select * from user where name like '张%'
此时如果传入的参数是 “张”
如果使用${}
:select * from user where name like '${value}%'
生成的 sql 语句:select * from user where name like '张%'
如果使用#{}
:select * from user where name like #{value}"%"
生成的 sql 语句:select * from user where name like '张'"%"
如果传入的参数是 “张 %”
使用#{}
:select * from user where name like #{value}
生成的 sql 语句:select * from user where name like '张%'
使用${}
:select * from user where name like '${value}'
生成的 sql 语句:select * from user where name like '张%'
通过上面的 SQL 语句我们能够发现#{}
是会加上双引号,而${}
匹配的是真实的值。
还有一点就是如果使用${}
的话,里面必须要填 value,即:${value}
,#{}
则随意
1.5.2 什么情况下用${}
?
场景举例:
代码测试:
执行之后,发现执行成功
我们可以切换一下,把${}
改成#{}
,会出现 SQL 语法错误的异常
1.6 总结
1.6.1 SQL 注入问题
MyBatis 的#{}
之所以能够预防 SQL 注入是因为底层使用了 PreparedStatement 类的 setString() 方法来设置参数,此方法会获取传递进来的参数的每个字符,然后进行循环对比,如果发现有敏感字符(如:单引号、双引号等),则会在前面加上一个’/‘代表转义此符号,让其变为一个普通的字符串,不参与 SQL 语句的生成,达到防止 SQL 注入的效果。
其次${}
本身设计的初衷就是为了参与 SQL 语句的语法生成,自然而然会导致 SQL 注入的问题(不会考虑字符过滤问题)。
1.6.2 #{}
和${}
用法总结
1)#{}
在使用时,会根据传递进来的值来选择是否加上双引号,因此我们传递参数的时候一般都是直接传递,不用加双引号,${}
则不会,我们需要手动加
2)在传递一个参数时,我们说了#{}
中可以写任意的值,${}
则必须使用 value;即:${value}
3)#{}
针对 SQL 注入进行了字符过滤,${}
则只是作为普通传值,并没有考虑到这些问题
4)#{}
的应用场景是为给 SQL 语句的 where 字句传递条件值,${}
的应用场景是为了传递一些需要参与 SQL 语句语法生成的值。